namespace DependencyLib
{
  using System.Collections;
  using System.Collections.Generic;
  using CrossFlow.GraphLib;
  using System.IO;
  using System.Reflection;

  public class GraphDelta
  {
    public GraphDelta()
    {
      Vertexes = new List<Vertex>();
      Edges = new List<Edge>();
    }

    public List<Vertex> Vertexes;
    public List<Edge> Edges;
  }

  public delegate void OnReferenceMembershipEventHandler(Package package);
  public delegate void OnMappedPackageEventHandler(MappedPackage mapped);

  public interface IDependencyProvider : System.IDisposable
  {
    DirectedGraph CreateGraph();
    GraphDelta AppendToGraph(DirectedGraph graph);
    event OnReferenceMembershipEventHandler OnReferenceIncluded;
    event OnReferenceMembershipEventHandler OnReferenceExcluded;
    event OnMappedPackageEventHandler OnMappedPackage;
  }

  /// <summary>
  /// Contains Template Methods so that derived classes provide instances of
  /// DigraphInfo from their own dependency crawling so this class can build or increment a digraph.
  /// EdgeInfo, VertexInfo and DigraphInfo are ancillary to build a proper digraph:CrossFlow.GraphLib.DirectedGraph
  /// </summary>
  public abstract class TemplatedBaseDependencyProvider : IDependencyProvider
  {
    public class EdgeInfo
    {
      internal string Name, Source, Target;
      internal object Data;

      public EdgeInfo(string name, string source, string target, object data)
      {
        this.Name = name;
        this.Source = source;
        this.Target = target;
        this.Data = data;
      }
    }

    public class VertexInfo
    {
      internal string Name;
      internal object Data;

      public VertexInfo(string name) : this(name, null) { }
      public VertexInfo(string name, object data)
      {
        this.Name = name;
        this.Data = data;
      }
    }

    public class DigraphInfo : VertexInfo
    {
      internal IEnumerable<VertexInfo> Vertexes;
      internal IEnumerable<EdgeInfo> Edges;

      public DigraphInfo(string name, object data, IEnumerable<VertexInfo> vertexes, IEnumerable<EdgeInfo> edges) : base(name, data)
      {
        this.Vertexes = vertexes;
        this.Edges = edges;
      }
    }

    protected internal class GraphAccreation
    {
      protected internal GraphAccreation(CrossFlow.GraphLib.DirectedGraph incrementing_graph)
      {
        NewOrIncrementingGraph = incrementing_graph;
        DeltaNodes = new GraphDelta();
      }

      protected internal CrossFlow.GraphLib.DirectedGraph NewOrIncrementingGraph;
      protected internal GraphDelta DeltaNodes;
    }

    public event OnReferenceMembershipEventHandler OnReferenceIncluded;
    public event OnReferenceMembershipEventHandler OnReferenceExcluded;
    public event OnMappedPackageEventHandler OnMappedPackage;

    protected void NotifyMappedPackage(MappedPackage mapped)
    {
      if (OnMappedPackage != null)
        OnMappedPackage(mapped);
    }

    protected void NotifyReferenceExcluded(Package package)
    {
      if (OnReferenceExcluded != null)
        OnReferenceExcluded(package);
    }
    protected void NotifyReferenceIncluded(Package package)
    {
      if (OnReferenceIncluded != null)
        OnReferenceIncluded(package);
    }

    /// <summary>
    /// This is the Template Method for creation of a digraph:CrossFlow.GraphLib.DirectedGraph
    /// </summary>
    /// <returns>The new digraph</returns>
    public virtual CrossFlow.GraphLib.DirectedGraph CreateGraph()
    {
      DigraphInfo digraph_info = GetDigraphInfoFromCurrentDependencyProvider();
      GraphAccreation accreation = BuildGraphAccreationFrom(digraph_info);
      return accreation.NewOrIncrementingGraph;
    }

    /// <summary>
    /// This is the Template Method for accreation of an existing digraph:CrossFlow.GraphLib.DirectedGraph
    /// </summary>
    /// <param name="basegraph">
    /// Graph that is the subject of accretion with new graph info
    /// from the current Dependency Provider
    /// </param>
    /// <returns>Appended graph nodes</returns>
    public virtual GraphDelta AppendToGraph(CrossFlow.GraphLib.DirectedGraph incrementing_graph)
    {
      DigraphInfo sub_digraph = GetDigraphInfoFromCurrentDependencyProvider();
      GraphAccreation accreation = IncrementGraph(sub_digraph, incrementing_graph);
      return accreation.DeltaNodes;
    }

    protected virtual GraphAccreation BuildGraphAccreationFrom(DigraphInfo digraph_info)
    {
      DirectedGraph newgraph = new CrossFlow.GraphLib.DirectedGraph(digraph_info.Name, digraph_info.Data);
      return IncrementGraph(digraph_info, newgraph);
    }

    /// <summary>
    /// Add new nodes to an existing digraph:CrossFlow.GraphLib.DirectedGraph
    /// </summary>
    /// <param name="digraph_info">New nodes info</param>
    /// <param name="incrementing_graph">Graph target of accreation</param>
    /// <returns>Accreation info</returns>
    protected virtual GraphAccreation IncrementGraph(DigraphInfo digraph_info, CrossFlow.GraphLib.DirectedGraph incrementing_graph)
    {
      GraphAccreation result = new GraphAccreation(incrementing_graph);
      if (digraph_info.Vertexes != null)
        foreach (VertexInfo vertex_info in digraph_info.Vertexes)
        {
          if (result.NewOrIncrementingGraph.ContainsVertex(vertex_info.Name)) continue;
          DigraphInfo subdigraph_info = vertex_info as DigraphInfo;
          Vertex vertex = subdigraph_info != null ?
            new SubDigraph(subdigraph_info.Name, BuildGraphAccreationFrom(subdigraph_info).NewOrIncrementingGraph, subdigraph_info.Data)
            :
            new Vertex(vertex_info.Name, vertex_info.Data);
          result.NewOrIncrementingGraph.AddNodes(vertex);
          result.DeltaNodes.Vertexes.Add(vertex);
        }
      if (digraph_info.Edges != null)
        foreach (EdgeInfo edge_info in digraph_info.Edges)
        {
          if (!result.NewOrIncrementingGraph.ContainsVertex(edge_info.Source))
            throw new System.ArgumentOutOfRangeException("Source Vertex", edge_info.Source, "not found.");
          if (!result.NewOrIncrementingGraph.ContainsVertex(edge_info.Target))
            throw new System.ArgumentOutOfRangeException("Target Vertex", edge_info.Target, "not found.");
          Vertex source = result.NewOrIncrementingGraph[edge_info.Source];
          Vertex target = result.NewOrIncrementingGraph[edge_info.Target];
          if (IsThereAnExistingEdgeBetweenTheseSourceAndTarget(source, target, result.NewOrIncrementingGraph)) continue;
          Edge edge = new Edge(edge_info.Name, source, target, edge_info.Data);
          result.NewOrIncrementingGraph.AddNodes(edge);
          result.DeltaNodes.Edges.Add(edge);
        }
      return result;
    }

    protected bool IsThereAnExistingEdgeBetweenTheseSourceAndTarget(Vertex source, Vertex target, CrossFlow.GraphLib.DirectedGraph incrementing_graph)
    {
      foreach (Edge edge in incrementing_graph.Edges)
        if (edge.Source.Name == source.Name && edge.Target.Name == target.Name) return true;
      return false;
    }

    public abstract DigraphInfo GetDigraphInfoFromCurrentDependencyProvider();
    public abstract void Dispose();
  }
}